package com.zhuo.common.service;

import com.zhuo.common.config.StorageProperties;
import com.zhuo.common.domain.storage.UploadOptions;
import com.zhuo.common.domain.storage.UploadResult;
import com.zhuo.common.exception.StorageException;
import com.zhuo.common.utils.StringUtil;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.UUID;

/**
 * 存储服务默认抽象实现
 * <p>
 * 提供文件路径、File、MultipartFile、InputStream 等多种上传方式的默认实现。
 * 子类（如 {@link com.zhuo.common.service.tencent.TencentCosService} TencentCosService / MinioStorageService）只需实现方法
 * </p>
 */
public abstract class DefaultStorageService implements StorageService {

    protected StorageProperties storageProperties;

    public DefaultStorageService(StorageProperties storageProperties) {
        this.storageProperties = storageProperties;
    }

    /* ======================== 上传核心方法 ======================== */

    /**
     * 抽象类统一执行上传方法
     */
    private UploadResult execUpload(InputStream inputStream, UploadOptions options) {
        // 上传参数未配置的按默认配置
        options = prepareUploadOptions(options);
        // 生成最终 key
        String key = buildObjectKey(options);
        options.setKey(key);

        // 调用子类实现上传逻辑
        return doUpload(inputStream, options);
    }

    /**
     * 核心上传逻辑，需由子类实现（COS、OSS、MinIO等）
     *
     * @param inputStream 输入流
     * @param options     上传参数（文件夹、文件名、是否带日期路径、是否使用UUID等）
     * @return 上传结果（是否成功、key等）
     */
    protected abstract UploadResult doUpload(InputStream inputStream, UploadOptions options);

    /* ======================== 接口方法 ======================== */

    @Override
    public UploadResult upload(String path) {
        return upload(path, new UploadOptions());
    }

    @Override
    public UploadResult upload(String path, String folder) {
        return upload(path, new UploadOptions(folder));
    }

    @Override
    public UploadResult upload(String path, UploadOptions uploadOptions) {
        File file = new File(path);
        return upload(file, uploadOptions);
    }

    @Override
    public UploadResult upload(File file) {
        return upload(file, new UploadOptions());
    }

    @Override
    public UploadResult upload(File file, String folder) {
        return upload(file, new UploadOptions(folder));
    }

    @Override
    public UploadResult upload(File file, UploadOptions uploadOptions) {
        if (uploadOptions == null) {
            uploadOptions = buildUploadOptions(null);
        }

        // 设置默认文件名
        if (uploadOptions.getFileName() == null) {
            uploadOptions.setFileName(file.getName());
        }

        // 设置文件大小
        uploadOptions.setFileSize(file.length());

        try (InputStream inputStream = Files.newInputStream(file.toPath())) {
            return execUpload(inputStream, uploadOptions);
        } catch (Exception e) {
            throw new StorageException("文件上传失败: " + file.getAbsolutePath(), e);
        }
    }

    @Override
    public UploadResult upload(MultipartFile file) {
        return upload(file, new UploadOptions());
    }

    @Override
    public UploadResult upload(MultipartFile file, String folder) {
        return upload(file, new UploadOptions(folder));
    }

    @Override
    public UploadResult upload(MultipartFile file, UploadOptions uploadOptions) {
        if (uploadOptions == null) {
            uploadOptions = buildUploadOptions(null);
        }
        if (uploadOptions.getFileName() == null) {
            uploadOptions.setFileName(file.getOriginalFilename());
        }

        // 处理文件大小
        uploadOptions.setFileSize(file.getSize());

        try (InputStream inputStream = file.getInputStream()) {
            return execUpload(inputStream, uploadOptions);
        } catch (Exception e) {
            throw new StorageException("MultipartFile 上传失败", e);
        }
    }

    @Override
    public UploadResult upload(InputStream inputStream, String fileName) {
        return upload(inputStream, buildUploadOptions(fileName));
    }

    @Override
    public UploadResult upload(InputStream inputStream, String fileName, String folder) {
        UploadOptions uploadOptions = buildUploadOptions(fileName);
        uploadOptions.setFolder(folder);
        return upload(inputStream, uploadOptions);
    }

    @Override
    public UploadResult upload(InputStream inputStream, UploadOptions uploadOptions) {
        if (uploadOptions == null) uploadOptions = buildUploadOptions(null);
        return execUpload(inputStream, uploadOptions);
    }

    /* ======================== 工具方法 ======================== */

    /**
     * 根据配置构建上传选项
     */
    protected UploadOptions buildUploadOptions(String fileName) {
        UploadOptions uploadOptions = new UploadOptions();
        uploadOptions.setFileName(fileName);
        return uploadOptions;
    }

    /**
     * 上传参数未配置的按默认配置
     */
    protected UploadOptions prepareUploadOptions(UploadOptions options) {
        if (options == null) {
            options = new UploadOptions();
        }

        // folder 统一
        String normalizedFolder = buildFolderPath(options.getFolder());
        options.setFolder(normalizedFolder);

        // 文件名处理
        if (!StringUtil.hasText(options.getFileName())) {
            options.setFileName(UUID.randomUUID().toString());
        } else {
            options.setFileName(options.getFileName().trim());
        }

        // 是否加日期前缀
        if (options.getDatePrefix() == null) {
            options.setDatePrefix(storageProperties.getAutoDatePrefix());
        }

        // 是否用 UUID
        if (options.getUseUUID() == null) {
            options.setUseUUID(storageProperties.getAutoUseUUID());
        }

        return options;
    }

    /**
     * 生成最终存储 key
     * 默认实现为：
     * 如果有 key，则直接使用；
     * 否则按 folder + 日期前缀 + 文件名 拼接。
     * 若子类 key 有特殊需求，可重写此方法。
     */
    protected String buildObjectKey(UploadOptions options) {
        // 有key直接返回
        if (StringUtil.hasText(options.getKey())) {
            return options.getKey();
        }

        StringBuilder key = new StringBuilder();

        // 拼接 folder
        if (StringUtil.hasText(options.getFolder())) {
            key.append(options.getFolder());
        }

        // 拼接日期前缀
        if (Boolean.TRUE.equals(options.getDatePrefix())) {
            String datePath = new java.text.SimpleDateFormat("yyyy/MM/dd").format(new java.util.Date());
            key.append(datePath).append("/");
        }

        // 拼接文件名（考虑 UUID 前缀）
        String fileName = options.getFileName();
        if (Boolean.TRUE.equals(options.getUseUUID())) {
            String uuid = UUID.randomUUID().toString().replace("-", "");
            int dotIndex = fileName.lastIndexOf('.');
            if (dotIndex != -1) {
//                String name = fileName.substring(0, dotIndex);
                String ext = fileName.substring(dotIndex);
                fileName = uuid + ext;
            } else {
                fileName = fileName + "_" + uuid;
            }
        }
        key.append(fileName);

        return key.toString();
    }

    /**
     * 生成最终 folder
     * 默认实现为：
     * - null → ""
     * - 去掉前缀 '/'
     * - 保证以 '/' 结尾（除非为空）
     * 若子类 folder 有特殊需求，可重写该方法。
     */
    protected String buildFolderPath(String folder) {
        if (folder == null) return "";
        String f = folder.trim();
        if (f.isEmpty()) return "";
        // 去掉开头的 '/'
        while (f.startsWith("/")) {
            f = f.substring(1);
        }
        // 保证结尾有 '/'
        if (!f.endsWith("/")) {
            f = f + "/";
        }
        return f;
    }

    /**
     * 根据文件名自动推测 content-type
     */
    protected String detectContentType(String fileName) {
        if (fileName == null) return "application/octet-stream";
        String lower = fileName.toLowerCase();
        if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
        if (lower.endsWith(".png")) return "image/png";
        if (lower.endsWith(".gif")) return "image/gif";
        if (lower.endsWith(".mp4")) return "video/mp4";
        if (lower.endsWith(".pdf")) return "application/pdf";
        if (lower.endsWith(".txt")) return "text/plain";
        return "application/octet-stream";
    }
}
